package crmdna.client.user;

import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import com.googlecode.objectify.ObjectifyFilter;
import com.googlecode.objectify.Work;

import crmdna.client.sequence.Sequence;
import crmdna.client.sequence.Sequence.SequenceType;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.crmdna.Client;

public class UserCore {		
	
	public static final String SUPER_USER = "sathyanarayanant@gmail.com";
	
	public static class UserProp {
		public String email;
		public TreeSet<String> privileges = new TreeSet<>(); 
		//format is: <resource type>||<resource id>||<action>
		//eg: group||1||read, group||2||write
		
		public long groupId;		
		public long userId;
	}
	
	private static final String DELIMITER = "||";
	
	static UserProp create(String namespace, String email, long groupId) {
		
		Utils.ensureValidEmail(email);
		email = email.toLowerCase();	
		
		UserEntity entity = ofy(namespace).load().type(UserEntity.class)
				.id(email).get();
		
		if (null != entity)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, 
					"There is already an user with email [" + email + "]");
		
		UserEntity userEntity = new UserEntity();
		userEntity.email = email;
		userEntity.userId = Sequence.getNext(namespace, SequenceType.USER);
		userEntity.groupId = groupId;
		
		ofy(namespace).save().entity(userEntity).now();
		
		return userEntity.toProp();
	}
	
	static UserProp updateEmail(String namespace, String existingEmail, 
			String newEmail) {
						
		Utils.ensureValidEmail(newEmail);
				
		existingEmail = existingEmail.toLowerCase();
		newEmail = newEmail.toLowerCase();
		
		UserEntity existingEntity = UserCore.safeGet(namespace, existingEmail);
		
		if (existingEmail.equals(newEmail))
			return existingEntity.toProp();
		
		//newEmail should not already exist
		if (null != get(namespace, newEmail))
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, 
					"There is already an user with email [" + newEmail + "]");				
		
		UserEntity newEntity = new UserEntity();
		newEntity.email = newEmail;
		newEntity.userId = existingEntity.userId;
		newEntity.groupId = existingEntity.groupId;
		newEntity.privileges = existingEntity.privileges;
				
		ofy(namespace).save().entity(newEntity);			
		ofy(namespace).delete().entity(existingEntity);
		ObjectifyFilter.complete();
		
		return newEntity.toProp();
	}
	
	static UserProp updateGroup(final String namespace, final String email, final long newGroupId) {																	
		
		//email should be present
		safeGet(namespace, email.toLowerCase());
						
		UserProp userProp = ofy(namespace).transact(new Work<UserProp>() {
			@Override
			public UserProp run() {
				UserEntity userEntity = ofy(namespace).load().type(UserEntity.class)
						.id(email.toLowerCase()).get();
				if (null == userEntity)
					throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
							"There is no user with email [" + email + "]");
				
				userEntity.groupId = newGroupId;
				ofy(namespace).save().entity(userEntity).now();
				return userEntity.toProp();				
			}			
		});
		
		return userProp;							
	}
	
	public static UserEntity get(String namespace, String email) {
	
		email = email.toLowerCase();
		UserEntity entity = ofy(namespace).load().type(UserEntity.class).id(email).get();
		
		if (entity == null)
			return null;
		
		return entity;		
	}
	
	public static UserEntity safeGet(String client, String email) {
		UserEntity userEntity = get(client, email);
		
		if (null == userEntity)
			throw new APIException(Status.ERROR_INVALID_USER, 
					"User [" + email + "] is not valid");
		
		return userEntity;		
	}
	
	public static List<UserProp> getAll(String namespace) {
		Client.ensureValidClient(namespace);
		
		List<UserEntity> entities = ofy(namespace).load().type(UserEntity.class).order("email").list();
		
		List<UserProp> props = new ArrayList<>();
		for (UserEntity entity : entities) {
			props.add(entity.toProp());
		}
		
		return props;
	}
	
	public static List<UserProp> getAllForGroup(String namespace, long groupId) {
				
		List<UserProp> all = getAll(namespace);
		
		List<UserProp> userProps = new ArrayList<>();
		for (UserProp userProp : all) {
			if (userProp.groupId == groupId)
				userProps.add(userProp);
		}
		
		return userProps;
	}
	
	static UserProp addOrDeletePrivilege(final String namespace, final String email, 
			final String resourceType, final long resourceId, final String action, final boolean add) {
			
		safeGet(namespace, email.toLowerCase()); //ensures user is valid
		
		UserProp userProp = ofy(namespace).transact(new Work<UserProp>() {

			@Override
			public UserProp run() {				
				UserEntity userEntity = ofy(namespace).load().type(UserEntity.class)
						.id(email.toLowerCase()).get();
				
				if (null == userEntity)
					throw new APIException(Status.ERROR_INVALID_USER, 
							"Invalid user [" + email + "]");
				
				String privilege = getPrivilege(resourceType, resourceId, action);
				
				boolean change;
				if (add)
					change = userEntity.privileges.add(privilege);
				else
					change = userEntity.privileges.remove(privilege);
					
				if (change)
					ofy(namespace).save().entity(userEntity).now();
				
				return userEntity.toProp();
			}
		});	
		
		return userProp;		
	}
	
	static Set<Long> getResources(String namespace, String email, String resourceType, 
			String action) {
		
		UserEntity entity = safeGet(namespace, email.toLowerCase()); //ensures user is valid
		
		Set<Long> resources = new HashSet<>();
		for (String privilege : entity.privileges) {
			if ((privilege != null) && privilege.contains(resourceType) && privilege.contains(action)) {
				//format is: <resource type>||<resource id>||<action>
				long resource = getResourceFromPrivilege(privilege);
				resources.add(resource);
			}
		}
		
		return resources;				
	}
	
	static long getResourceFromPrivilege(String privilege) {
		String resource = privilege.split("\\|\\|")[1];
		return Long.parseLong(resource);
	}
	
	static UserProp clonePrivileges(final String namespace, final String sourceEmail, final String targetEmail) {		
		
		if (null == sourceEmail)
			Utils.throwIncorrectSpecException("sourceUser cannot be null");			 					
		
		if (null == targetEmail)	
			Utils.throwIncorrectSpecException("targetUser cannot be null");	
		
		safeGet(namespace, sourceEmail.toLowerCase());
		safeGet(namespace, targetEmail.toLowerCase());
		
		UserProp userProp = ofy(namespace).transact(new Work<UserProp>() {

			@Override
			public UserProp run() {
				UserEntity sourceEntity = ofy(namespace).load().type(UserEntity.class)
						.id(sourceEmail.toLowerCase()).get();
				if (null == sourceEntity)
					throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
							"Source email [" + sourceEmail + "] does not exist");
				
				UserEntity targetEntity = ofy(namespace).load().type(UserEntity.class)
						.id(targetEmail.toLowerCase()).get();
				if (null == targetEntity)
					throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
							"Target email [" + targetEmail + "] does not exist");
				
				targetEntity.privileges = sourceEntity.privileges;
				ofy(namespace).save().entity(targetEntity).now();
				return targetEntity.toProp();
			}
		});
		
		return userProp;					
	}
	
	public static void ensureValidUser(String client, String login) {
		
		if (null == login)
			throw new APIException(Status.ERROR_LOGIN_REQUIRED, 
					"Email cannot be null or empty");
		
		if (login.equals(SUPER_USER))
			return;
		
		login = login.toLowerCase();
		UserEntity userEntity = ofy(client).load().type(UserEntity.class)
				.id(login).get();
		
		if (null == userEntity)
			throw new APIException(Status.ERROR_INVALID_USER, 
					"Invalid user [" + login + "]");
	}
		
	static void ensureAccess(String client, String login, String resourceType, 
			long resourceId, String action) {		
						
		if (null == login)
			throw new APIException(Status.ERROR_LOGIN_REQUIRED, 
					"Email cannot be null or empty");
		
		if (login.equals(SUPER_USER))
			return;
		
		login = login.toLowerCase();
		UserEntity userEntity = ofy(client).load().type(UserEntity.class)
				.id(login).get();
		
		if (null == userEntity)
			throw new APIException(Status.ERROR_INVALID_USER, 
					"Invalid user [" + login + "]");
		
		String privilege = getPrivilege(resourceType, resourceId, action);
		if (! userEntity.privileges.contains(privilege))
			throw new APIException(Status.ERROR_INSUFFICIENT_PERMISSION, 
					"User [" + login + "] does not have the required privilage - [" 
					+ privilege + "]. (Privilege format is <Resource type>||<Resource id>||<Action>)");		
	}		
	
	static String getPrivilege(String resourceType, long resourceId, 
			String action) {
				 
		return resourceType.toString() + DELIMITER + resourceId + DELIMITER + action.toString();
	}	
}